package com.study.chunguard.common.utils.tree;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.text.CharSequenceUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import java.io.PrintWriter;
import java.util.*;
import java.util.function.Consumer;

/**
 * 树节点接口，增加了获取子节点、父节点、遍历、过滤等方法
 *
 * @param <T> ID类型
 * @author zhaowenhao
 * @since 2023-05-09
 */
@SuppressWarnings("unchecked")
public interface TreeNode<T, N extends TreeNode<T, N>> extends Node<T> {

    /**
     * 创建一个空节点
     *
     * @return 一个空节点
     */
    N createEmpty();

    /**
     * 获取父节点
     *
     * @return 获取父节点
     */
    N getParent();

    /**
     * 设置父节点
     *
     * @param parent 设置父节点
     */
    void setParent(N parent);

    /**
     * 获取父节点
     *
     * @return 获取父节点
     */
    N getParentDetail();

    /**
     * 设置父节点
     *
     * @param parent 设置父节点
     */
    void setParentDetail(N parent);

    /**
     * 获取节点子节点
     *
     * @return 获取节点子节点
     */
    List<N> getChildren();

    /**
     * 设置节点子节点
     *
     * @param children 子节点
     */
    void setChildren(List<N> children);

    /**
     * 增加子节点，同时关联子节点的父节点为当前节点
     *
     * @param children 子节点列表
     */
    default void addChildren(List<N> children) {
        if (!CollectionUtils.isEmpty(children)) {
            List<N> childrenList = getChildren();
            if (null == childrenList) {
                childrenList = new ArrayList<>();
            }

            for (N child : children) {
                child.setParent((N) this);
                childrenList.add(child);
            }
            setChildren(childrenList);
        }
    }


    /**
     * 获取ID对应的节点，如果有多个ID相同的节点，只返回第一个。<br>
     * 此方法只查找此节点及子节点，采用广度优先遍历。
     *
     * @param id ID
     * @return 节点
     * @since 5.2.4
     */
    default N getNode(T id) {
        return TreeUtil.getNode((N) this, id);
    }

    /**
     * 获取所有父节点名称列表
     *
     * <p>
     * 比如有个人在研发1部，他上面有研发部，接着上面有技术中心<br>
     * 返回结果就是：[研发一部, 研发中心, 技术中心]
     *
     * @param id 节点ID
     * @return 所有父节点名称列表
     * @since 5.2.4
     */
    default List<CharSequence> getParentNames(T id) {
        return getParentNames(id, false);
    }

    /**
     * 获取所有父节点名称列表
     *
     * <p>
     * 比如有个人在研发1部，他上面有研发部，接着上面有技术中心<br>
     * 返回结果就是：[研发一部, 研发中心, 技术中心]
     *
     * @param id                 节点ID
     * @param includeCurrentNode 是否包含当前节点的名称
     * @return 所有父节点名称列表
     * @since 5.2.4
     */
    default List<CharSequence> getParentNames(T id, boolean includeCurrentNode) {
        return TreeUtil.getParentNames(getNode(id), includeCurrentNode);
    }

    /**
     * 获取所有父节点名称列表
     *
     * <p>
     * 比如有个人在研发1部，他上面有研发部，接着上面有技术中心<br>
     * 返回结果就是：[研发一部, 研发中心, 技术中心]
     *
     * @return 所有父节点名称列表
     * @since 5.2.4
     */
    default List<CharSequence> getParentNames() {
        return getParentNames(false);
    }

    /**
     * 获取所有父节点名称列表
     *
     * <p>
     * 比如有个人在研发1部，他上面有研发部，接着上面有技术中心<br>
     * 返回结果就是：[研发一部, 研发中心, 技术中心]
     *
     * @param includeCurrentNode 是否包含当前节点的名称
     * @return 所有父节点名称列表
     * @since 5.2.4
     */
    default List<CharSequence> getParentNames(boolean includeCurrentNode) {
        return TreeUtil.getParentNames((N) this, includeCurrentNode);
    }

    /**
     * 获取所有父节点ID列表
     *
     * @return 所有父节点ID列表，node为null返回空List
     */
    default List<T> getParentIds() {
        return getParentIds(false);
    }

    /**
     * 获取所有父节点ID列表
     *
     * @param includeCurrentNode 是否包含当前节点的名称
     * @return 所有父节点ID列表，node为null返回空List
     */
    default List<T> getParentIds(boolean includeCurrentNode) {
        return TreeUtil.getParentIds((N) this, includeCurrentNode);
    }

    /**
     * 获取所有父节点ID列表
     *
     * @return 所有父节点ID列表，node为null返回空List
     */
    default List<T> getParentIds(T id) {
        return getParentIds(id, false);
    }

    /**
     * 获取所有父节点ID列表
     *
     * @param includeCurrentNode 是否包含当前节点的名称
     * @return 所有父节点ID列表，node为null返回空List
     */
    default List<T> getParentIds(T id, boolean includeCurrentNode) {
        return TreeUtil.getParentIds(getNode(id), includeCurrentNode);
    }

    /**
     * 是否有子节点，无子节点则此为叶子节点
     *
     * @return 是否有子节点
     * @since 5.7.17
     */
    default boolean hasChild() {
        return CollUtil.isNotEmpty(getChildren());
    }

    /**
     * 递归树并处理子树下的节点：
     *
     * @param consumer 节点处理器
     * @since 5.7.16
     */
    default void walk(Consumer<N> consumer) {
        consumer.accept((N) this);
        final List<N> children = getChildren();
        if (CollUtil.isNotEmpty(children)) {
            children.forEach((treeNode) -> treeNode.walk(consumer));
        }
    }

    /**
     * 递归过滤并生成新的树<br>
     * 通过{@link Filter}指定的过滤规则，本节点满足过滤条件，则保留当前节点，并向上递归保留其所有父节点
     *
     * @param filter 节点过滤规则函数，只需处理本级节点本身即可
     * @return 过滤后的节点，{@code null} 表示不满足过滤要求，丢弃之
     * @see #filter(Filter)
     * @since 5.7.17
     */
    default N filterNew(Filter<N> filter) {
        return cloneTree().filter(filter);
    }

    /**
     * 递归过滤当前树，注意此方法会修改当前树<br>
     * 通过{@link Filter}指定的过滤规则，本节点满足过滤条件，则保留当前节点，并向上递归保留其所有父节点
     *
     * @param filter 节点过滤规则函数，只需处理本级节点本身即可
     * @return 过滤后的节点，{@code null} 表示不满足过滤要求，丢弃之
     * @see #filterNew(Filter)
     * @since 5.7.17
     */
    default N filter(Filter<N> filter) {
        Set<T> validIds = new HashSet<>();
        preOrderDfs(node -> {
            if (filter.accept(node)) {
                for (N curr = node; curr != null; curr = curr.getParent()) {
                    validIds.add(curr.getId());
                }
            }
        });

        preOrderDfs(node -> {
            if (!validIds.contains(node.getId())) {
                N parent = node.getParent();
                if (parent != null) {
                    List<N> children = parent.getChildren();
                    children.remove(node);
                    node.setParent(null);
                    parent.setChildren(children);
                }
            }
        });

        return (N) this;
    }

    /**
     * 遍历树，采用深度优先遍历（DFS）<br>
     *
     * @param callback 消费者函数
     */
    default void preOrderDfs(Consumer<N> callback) {
        preOrderDfs((N) this, callback);
    }

    /**
     * 遍历树，采用深度优先遍历（DFS）<br>
     *
     * @param node     树节点
     * @param callback 消费者函数
     */
    default void preOrderDfs(N node, Consumer<N> callback) {
        Deque<N> stack = new ArrayDeque<>();
        stack.push(node);

        while (!stack.isEmpty()) {
            N curr = stack.pop();
            callback.accept(curr);
            List<N> children = curr.getChildren();
            if (children != null) {
                for (int i = children.size() - 1; i >= 0; i--) {
                    stack.push(children.get(i));
                }
            }
        }
    }

    /**
     * 递归克隆当前节点（即克隆整个树，保留字段值）<br>
     * 注意，此方法只会克隆节点，节点属性如果是引用类型，不会克隆
     *
     * @return 新的节点
     * @since 5.7.17
     */
    default N cloneTree() {
        final N result = createEmpty();
        BeanUtils.copyProperties(this, result);
        result.setChildren(cloneChildren(result));
        return result;
    }

    /**
     * 递归复制子节点
     *
     * @param parentClone 父节点
     * @return 新的子节点列表
     */
    default List<N> cloneChildren(N parentClone) {
        final List<N> children = getChildren();
        if (null == children) {
            return null;
        }
        final List<N> newChildren = new ArrayList<>(children.size());
        children.forEach((child) -> {
            N childClone = child.cloneTree();
            childClone.setParent(parentClone);
            newChildren.add(childClone);
        });
        return newChildren;
    }

    /**
     * 打印
     *
     * @param treeNode 树
     * @param writer   Writer
     * @param intent   缩进量
     */
    static <N extends TreeNode<?, ?>> void printTree(N treeNode, PrintWriter writer, int intent) {
        writer.println(CharSequenceUtil.format("{}{}[{}]", CharSequenceUtil.repeat(CharPool.SPACE, intent), treeNode.getName(), treeNode.getId()));
        writer.flush();

        final List<N> children = (List<N>) treeNode.getChildren();
        if (CollUtil.isNotEmpty(children)) {
            for (N child : children) {
                printTree(child, writer, intent + 2);
            }
        }
    }

    /**
     * 是否有父节点
     *
     * @return 是否有父节点
     */
    default boolean hasParent() {
        return getParent() != null;
    }
}
